﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Dynamic;
using System.Reflection;
using System.Collections;

using King.Reflection;
using King.Extensions;

namespace King.Text {

    [TextNodeContainsContent]
    public sealed class TextStartLine : TextNode {
        public static TextStartLine Singleton = new TextStartLine();

        private TextStartLine() { }

        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            return null;
        }
    }

    [TextNodeMarkup]
    public sealed class TextWrap : TextNodes {

        private static readonly object Position = new object();

        private int m_lineLength;
        private int m_startColumn;
        private string m_wrapAfter;
        private string m_wrapString;
        private bool m_preserveOriginalNewLines;

        public TextWrap(int lineLength,
            int startColumn = 0,
            string wrapAfter = " ", 
            string wrapString = "\r\n",
            bool preserveOrininalNewLines = false) {

            m_startColumn = startColumn;
            m_preserveOriginalNewLines = preserveOrininalNewLines;
            m_lineLength = lineLength;
            m_wrapAfter = wrapAfter;
            if (m_wrapAfter != null)
                m_lineLength--;
            m_wrapString = wrapString;
        }

        protected internal override string Escape(TextNodeContext context, char character) {
            if (!m_preserveOriginalNewLines && Environment.NewLine.Contains(character))
                return character == '\n' ? " " : string.Empty;

            var position = context.GetValue<int>(Position, m_startColumn);
            context.SetValue(Position, position + 1);

            if (position >= m_lineLength) {
                
                string result = null;

                if (m_wrapAfter == null)
                    result = m_wrapString + character;

                else if (m_wrapAfter.Contains(character))
                    result = character + m_wrapString;

                if (result != null) {
                    var index = result.LastIndexOf("\n");
                    if (index == -1)
                        index = 0;

                    context.SetValue(Position, result.Substring(index + 1).Length);
                    return result;
                }
            }

            return null;
        }
    }

    public sealed class TextJoin : TextNodes {

        private object m_delimiter;
        private object m_prefix;
        private object m_suffix;
        private object m_lastDelimiter;
        private bool m_force;

        public TextJoin(object delimiter, 
            object prefix = null, 
            object suffix = null, 
            bool force = false, 
            object lastDelimiter = null) {

            m_lastDelimiter = lastDelimiter;
            m_delimiter = delimiter;
            m_prefix = prefix;
            m_suffix = suffix;
            m_force = force;
        }

        public TextJoin(string delimiter = " ", 
            object prefix = null, 
            object suffix = null, 
            bool force = false, 
            object lastDelimiter = null)
            : this((object)delimiter, prefix, suffix, force, lastDelimiter) {
        }

        protected internal override IEnumerable<object> Content() {
            if (m_force)
                return TextNode.ArbitraryContent;

            return Children();
        }
        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            
            var content = context.Flatten(Children());

            if (!m_force && !context.ContainsContent(content))
                yield break;

            var count = 0;
            if (m_lastDelimiter != null)
                count = content.Count();

            yield return m_prefix;

            bool first = true;
            foreach (var value in content) {
                count--;

                if (!context.ContainsContent(value))
                    continue;

                if (!first) {
                    if (count == 0 && m_lastDelimiter != null)
                        yield return m_lastDelimiter;
                    else
                        yield return m_delimiter;
                }

                first = false;
                yield return value;
            }

            yield return m_suffix;
        }
    }
    public sealed class TextSingleSpaced : TextNodes {
        
        private object m_prefix;
        private object m_suffix;

        public TextSingleSpaced(object prefix = null, object suffix = null) {
            m_prefix = prefix;
            m_suffix = suffix;
        }

        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            return new TextJoin(Environment.NewLine, prefix: m_prefix, suffix: m_suffix) { Children() };
        }
    }
    public sealed class TextDoubleSpaced : TextNodes {
        
        private object m_prefix;
        private object m_suffix;

        public TextDoubleSpaced(object prefix = null, object suffix = null) {
            m_prefix = prefix;
            m_suffix = suffix;
        }

        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            return new TextJoin(Environment.NewLine + Environment.NewLine, prefix: m_prefix, suffix: m_suffix) { Children() };
        }
    }
    public sealed class TextLine : TextNodes {

        protected internal override IEnumerable<object> Content() {
            return Children();
        }
        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            return new TextJoin(suffix: Environment.NewLine) { Children() };
        }
    }

    [TextNodeSubstituteTargets(typeof(TextStartLine))]
    public sealed class TextIndentReset : TextNodes {
        
        protected internal override IEnumerable<object> Substitute(TextNodeContext context, object value) {
            return Enumerable.Empty<object>();
        }
    }
    public sealed class TextIndentDefault : TextNodes {
        public static readonly object DefaultIndent = "    ";

        private object m_value;

        public TextIndentDefault(object value) {
            m_value = value;
        }

        public object Value {
            get { return m_value; }
        }
    }

    [TextNodeSubstituteTargets(typeof(TextStartLine))]
    public sealed class TextIndent : TextNodes {

        private object m_value;

        public TextIndent() {
        }
        public TextIndent(object value) {
            m_value = value;
        }

        protected internal override IEnumerable<object> Substitute(TextNodeContext context, object value) {
            yield return context.Substitute(value);

            if (m_value != null) {
                yield return m_value;

            } else {
                var defaultIndent = context.Ancestors<TextIndentDefault>().FirstOrDefault();
                if (defaultIndent == null)
                    yield return TextIndentDefault.DefaultIndent;
                else
                    yield return defaultIndent.Value;
            }
        }

        public object Value {
            get { return m_value; }
        }
    }
    public sealed class TextHeader : TextNodes {

        private object m_header;
        private object m_footer;

        public TextHeader(object header, object footer = null) {
            m_header = header;
            m_footer = footer;
        }

        internal protected override IEnumerable<object> Content() {
            return Children();
        }
        internal protected override IEnumerable<object> Children(TextNodeContext context) {
            return new TextSingleSpaced() {
                new TextNodes() { m_header },
                new TextIndent() {
                    new TextSingleSpaced() {
                        Children()
                    }
                },
                new TextNodes() { m_footer },
            };
        }
    }

    public sealed class TextCommandLine : TextNodes {
        
        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            return new TextJoin() {
                context.Flatten(Children()).Select(o =>
                    o is TextCommandLineArgument ? o :
                    new TextCommandLineArgument() { o }
                )
            };
        }
    }
    public sealed class TextCommandLineArgument : TextNodes {

        private object m_prefix;

        public TextCommandLineArgument(object prefix = null) {
            m_prefix = prefix;
        }

        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            yield return m_prefix;

            var argument = context.Evaluate(Children());
            var useQuotes = !(argument.StartsWith("\"") && argument.EndsWith("\"")) && argument.Any(o => char.IsWhiteSpace(o));

            if (useQuotes)
                yield return new TextNodes() { "\"", Children(), "\"" };
            else 
                yield return Children();
        }
    }

    public sealed class TextNameValue : TextNode {

        public static IEnumerable<KeyValuePair<object, object>> ToKeyValuePairs(object o) {
            if (o == null)
                return null;

            var dictionary = o as IDictionary;
            var keyValues = o as IEnumerable<KeyValuePair<object, object>>;

            if (dictionary != null)
                return dictionary.ToKeyValuePairs();

            else if (keyValues != null)
                return keyValues;

            return o.GetType().Get<FieldInfo>(isInstance: true).ToKeyValuePairs();
        }

        private IEnumerable<KeyValuePair<object, object>> m_nameValue;
        private object m_delimiter;
        private object m_prefix;
        private object m_suffix;
        private object m_join;

        public TextNameValue(object nameValue, 
            string delimiter = ", ", string join = " = ", 
            object prefix = null, object suffix = null) {

            m_nameValue = ToKeyValuePairs(nameValue);
            m_join = join;
            m_delimiter = delimiter;
            m_prefix = prefix;
            m_suffix = suffix;
        }

        protected internal override IEnumerable<object> Content() {
            if (m_nameValue.Any())
                yield return TextNode.ArbitraryContent;
        }
        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            return new TextJoin(m_delimiter, m_prefix, m_suffix) {
                m_nameValue.Select(o => new TextNodes() { o.Key, m_join, o.Value })
            };
        }
    }
}
